package br.com.bankai.koby.presentation.security;

import java.util.Collection;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.TextEscapeUtils;

import br.com.bankai.koby.repository.dom.DomPessoa;
import br.com.bankai.koby.repository.entity.simple.UsuarioSimple;
import br.com.bankai.koby.service.exception.NoResultFoundException;
import br.com.bankai.koby.service.impl.UsuarioService;

/**
 * 
 * @author Leonardo
 *
 */
public class AuthenticationFilter extends AbstractAuthenticationProcessingFilter {

	public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "j_username";
	public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "j_password";
	public static final String SPRING_SECURITY_SOURCE_ADDRESS_KEY = "j_sourceAddress";

	public static final String SPRING_SECURITY_LAST_USERNAME_KEY = "SPRING_SECURITY_LAST_USERNAME";

	private boolean postOnly = true;

	private static Logger LOGGER = LoggerFactory.getLogger(AuthenticationFilter.class);

	private static final int MAX_ATTEMPTS = 3;

	private UsuarioService usuarioService;

	public AuthenticationFilter() {
		super("/j_spring_security_check");
	}

	@Override
	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
		try {
			if (postOnly && !request.getMethod().equals("POST")) {
				throw new AuthenticationServiceException("");
			}

			String cpf = obtainCpf(request);
			if (StringUtils.isNotEmpty(cpf)) {
				cpf = cpf.replaceAll("[-_ .]", "");
			}
			String password = obtainPassword(request);
			String sourceAddress = obtainSourceAddress(request);

			if (StringUtils.isEmpty(cpf)) {
				throw new AuthenticationServiceException("Favor informar o CPF");
			}
			if (StringUtils.isEmpty(password)) {
				throw new AuthenticationServiceException("Favor informar a senha");
			}

			LOGGER.debug("Autenticando usuario com usuario {} do ip {}", cpf, sourceAddress);

			cpf = StringUtils.defaultString(cpf).trim();
			password = StringUtils.defaultString(password).trim();
			sourceAddress = StringUtils.defaultString(sourceAddress).trim();

			UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(cpf, password);

			HttpSession session = request.getSession(false);

			if (session != null || getAllowSessionCreation()) {
				request.getSession().setAttribute(SPRING_SECURITY_LAST_USERNAME_KEY, TextEscapeUtils.escapeEntities(cpf));
			}

			setDetails(request, authRequest);

			UsuarioSimple usuario = null;
			try {
				usuario = usuarioService.find(new UsuarioSimple(null, cpf, null));
			} catch (NoResultFoundException e) {
				throw new BadCredentialsException("Usuario nao encontrado");
			}
			
			if (usuario.getStatus() == DomPessoa.DomStatusPessoaFisica.PENDENTE) {
				throw new AuthenticationServiceException("Usuário pendente, favor realizar o primeiro acesso.");
			}
			if (usuario.getStatus() == DomPessoa.DomStatusPessoaFisica.BLOQUEADO) {
				throw new AuthenticationServiceException("Usuário bloqueado, contate o administrador.");
			}

			UsernamePasswordAuthenticationToken authenticatedUser = null;
			try {
				authenticatedUser = (UsernamePasswordAuthenticationToken) this.getAuthenticationManager().authenticate(authRequest);
			} catch (BadCredentialsException e) {
				if (usuario.getStatus() != DomPessoa.DomStatusPessoaFisica.BLOQUEADO) {
					int tentativas = usuario.getQuantidadeTentativa() + 1;

					if (tentativas == MAX_ATTEMPTS) {
						tentativas = 0;
						usuarioService.updateStatus(usuario.getId(), DomPessoa.DomStatusPessoaFisica.BLOQUEADO);
					}

					usuarioService.updateQuantidadeTentativas(usuario.getId(), tentativas);

					if (tentativas == 0) {
						throw new AuthenticationServiceException("Usuário bloqueado, contate o administrador.");
					} else {
						throw e;
					}
				} else {
					throw e;
				}
			} catch (DisabledException e) {
				throw e;
			} catch (Throwable t) {
				LOGGER.error("Erro ao autenticar usuario", t);
				throw new AuthenticationServiceException("Erro ao realizar a autenticação, contate o administrador.");
			}

			usuarioService.updateQuantidadeTentativas(usuario.getId(), 0);

			UserInfo principal = prepareUserInfo((User) authenticatedUser.getPrincipal(), authenticatedUser.getAuthorities(), usuario, sourceAddress);

			UsernamePasswordAuthenticationToken returnUser = new UsernamePasswordAuthenticationToken(principal, authenticatedUser.getCredentials(),
					authenticatedUser.getAuthorities());
			returnUser.setDetails(authenticatedUser.getDetails());

			return returnUser;
		} catch (BadCredentialsException e) {
			throw new AuthenticationServiceException("Usuário e/ou senha incorreto(s).");
		} catch (DisabledException e) {
			throw new AuthenticationServiceException("Usuário com acesso desabilitado.");
		} catch (AuthenticationServiceException e) {
			throw e;
		} catch (Throwable t) {
			LOGGER.error("Erro ao autenticar usuario", t);
			throw new AuthenticationServiceException("Erro ao realizar a autenticação, contate o administrador.");
		}
	}

	private UserInfo prepareUserInfo(User user, Collection<GrantedAuthority> authorities, UsuarioSimple usuario, String sourceAddress) {
		UserInfo principal = new UserInfo(user.getUsername(), "", authorities, usuario);
		principal.setSourceAddress(sourceAddress);

		return principal;
	}

	private void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
		authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
	}

	private String obtainCpf(HttpServletRequest request) {
		return request.getParameter(SPRING_SECURITY_FORM_USERNAME_KEY);
	}

	private String obtainPassword(HttpServletRequest request) {
		return request.getParameter(SPRING_SECURITY_FORM_PASSWORD_KEY);
	}

	private String obtainSourceAddress(HttpServletRequest request) {
		return request.getRemoteAddr();
	}

	public void setUsuarioService(UsuarioService usuarioService) {
		this.usuarioService = usuarioService;
	}
}